package com.longma.server.service.impl;


import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.util.ReflectUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.extra.mail.MailAccount;
import cn.hutool.extra.mail.MailUtil;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.longma.server.constant.enums.*;
import com.longma.server.controller.WebSocketServer;
import com.longma.server.dao.AlarmRecordDAO;
import com.longma.server.dao.AlarmSettingDAO;
import com.longma.server.dao.OperatorDAO;
import com.longma.server.entity.*;
import com.longma.server.exception.BusinessException;
import com.longma.server.pojo.dto.AdvanceQueryConditionDTO;
import com.longma.server.pojo.vo.AlarmRecordVO;
import com.longma.server.pojo.vo.AlarmCountVO;
import com.longma.server.security.AESUtil;
import com.longma.server.service.*;
import com.longma.server.util.AuthContextUtil;
import com.longma.server.util.MyMailUtil;
import com.longma.server.util.MybatisPlusPageUtil;
import com.longma.server.util.WrappersUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.time.LocalDateTime;
import java.util.*;
import java.util.function.Function;
import java.util.stream.Collectors;

/**
 * <p>
 *  服务实现类
 * </p>
 *
 * @author HAIBO
 * @since 2021-04-28
 */
@Transactional
@Service
public class AlarmRecordServiceImpl  implements IAlarmRecordService {
    @Value("${website}")
    private String website;

    private static final String ALARM_URL_TAIL = "/api/alarm-record/checkbymail?code=";

    @Autowired
    private AlarmSettingDAO alarmSettingDAO;

    @Autowired
    private com.longma.server.dao.YuncangDAO yuncangDAO;

    @Autowired
    private IYuncangService yuncangService;

    @Autowired
    private OperatorDAO operatorDAO;

    @Autowired
    private MailAccount mailAccount;

    @Autowired
    private AlarmRecordDAO alarmRecordDAO;

    @Autowired
    private IYuncangUserService yuncangUserService;

    /**
     * 处理上报数据、数据如果异常、新增告警记录
     */
    @Override
    public void handleRealtimeData(RealtimeData data) {

        // 查看指定云仓的所有告警设置
        List<AlarmSetting> alarmSettings = alarmSettingDAO.list(Wrappers. <AlarmSetting>lambdaQuery().eq(AlarmSetting::getYuncangId, data.getYuncangId()));

        // 是否有告警
        boolean exception = false;

        // 遍历所有设置
        for (AlarmSetting alarmSetting:alarmSettings ) {
            // 利用反射获取指定字段值 voltage or current
            Object value = ReflectUtil.getFieldValue(data,alarmSetting.getName());
            if(value == null || value.equals(0)){
                continue;
            }

            // 转为数值类型
            Double dataReceived = Double.parseDouble(value.toString());

            // 数据异常
            if (dataReceived > alarmSetting.getMax() || dataReceived < alarmSetting.getMin()){
                // 相同云仓相同传感器不重复预警 并且未处理
                if (alarmRecordDAO.count(Wrappers.<AlarmRecord>lambdaQuery().eq(AlarmRecord::getYuncangId, alarmSetting.getYuncangId())
                        .eq(AlarmRecord::getAlarmId, alarmSetting.getId())
                .eq(AlarmRecord::getState, AlarmRecordStateEnum.UNHANDLED)) > 0){
                    continue;
                }

                // 获取云仓信息
                Yuncang yuncang = yuncangDAO.getById(alarmSetting.getYuncangId());

                // 生成告警信息
                AlarmRecord record = new AlarmRecord()
                                    .setAlarmTime(LocalDateTime.now())
                                    .setAlarmId(alarmSetting.getId())
                                    .setName(yuncang.getYuncangName()+ alarmSetting.getName())
                                    .setYuncangId(alarmSetting.getYuncangId())
                                    .setValue(dataReceived)
                                    .setState(AlarmRecordStateEnum.UNHANDLED)
                                    .setLevel(alarmSetting.getLevel())
                                    .setDetails(yuncang.getYuncangName()+ alarmSetting.getName()+"("+yuncang.getProvince()+yuncang.getCity()+")");


                // 保存
                alarmRecordDAO.save(record);

                // 如果开启邮件通知
                if(GeneralStateEnum.ENABLE == alarmSetting.getNotifyState()) {
                    if (NotifyTypeEnum.EMAIL == alarmSetting.getNotifyType()) {
                        sendMail(alarmSetting, yuncang.getYuncangName());
                    }
                }

                // 设置标志位
                exception = true;
               }
        }

        // 推送告警记录
        if(exception) {
            pushAlarmCount(data.getYuncangId());
        }else{
            // 无告警 云仓正常运行
            yuncangService.updateYuncangState(data.getYuncangId(), YuncangStateEnum.RUNNING);
        }
    }


    /**
     * 如果有告警记录生成、或者有告警记录被处理时调用
     * 1、获取告警记录所属云仓、查找关联的用户
     * 2、向在线有权限的用户推送告警信息
     * @param yuncangId: 云仓id
     */
    private void pushAlarmCount(Integer yuncangId){
        // 查找云仓关联的用户
        Set<String> loginNames  = yuncangUserService.getUsers(yuncangId).stream().map(SysUser::getLoginName).collect(Collectors.toSet());

        // 遍历所有链接
        for(Map.Entry<String,WebSocketServer> entry: WebSocketServer.getWebSocketServerMap().entrySet()){
            WebSocketServer webSocketServer = entry.getValue();
            // 获取登录用户
            String loginName = webSocketServer.getLoginName();
            // 需要通知
            if(loginNames.contains(loginName)){
                // 获取该用户的告警计数信息
                AlarmCountVO alarmCountVO =  getAlarmCount(loginName);
                webSocketServer.sendMessage(JSON.toJSONString(alarmCountVO));
            }
        }
    }


    /**
     * 根据告警记录id删除告警记录
     */
    @Override
    public boolean deleteAlarmRecord(Integer id){
        return alarmRecordDAO.removeById(id);
    }

    /*
     * 根据告警记录id处理告警记录  将告警记录标志为已处理，如果该设备没有告警记录 云仓标记为正常运行
     * @param id：告警记录id
     */
    @Override
    public void handleAlarmRecord(Integer id) {
        AlarmRecord record = alarmRecordDAO.getById(id);

        if (record == null){
            throw new BusinessException(ResponseCode.PARAM_NOT_VALID.setMessage("id不存在"));
        }

        record.setState(AlarmRecordStateEnum.HANDLED);
        alarmRecordDAO.updateById(record);

        // 推送告警记录
        pushAlarmCount(record.getYuncangId());
    }

    /**
     * 通过邮件处理告警记录
     */
    @Override
    public String handleAlarmRecordByMail(String code) {
        code = code.replace(" ", "+");
        String decryptStr = AESUtil.decrypt(code);
        JSONObject jsonObject = JSON.parseObject(decryptStr);

        // 获取告警记录id
        Integer id = Integer.parseInt((String)(jsonObject.get("alarmRecordId")));
        Date date = new Date((Long)jsonObject.get("time"));
        // 过期
        if (date.after(DateUtil.offsetDay(date, 1))){
            return "链接超过24小时，已失效。";
        }

        return alarmRecordProcess(id);
    }

    /**
     * 根据登录账号获取告警数量
     */
    @Override
    public AlarmCountVO getAlarmCount(String loginName) {
        AlarmCountVO res = new AlarmCountVO();

        // 获取云仓id
        List<Integer> yuncangIds = yuncangUserService.getYuncangs(loginName).stream().map(Yuncang::getId).collect(Collectors.toList());

        if (CollUtil.isEmpty(yuncangIds)){
            return res;
        }

        // 查询
        List<AlarmRecord> alarmRecords = alarmRecordDAO.list(Wrappers.<AlarmRecord>lambdaQuery().select(AlarmRecord::getLevel,AlarmRecord::getId).in(AlarmRecord::getYuncangId, yuncangIds)
        .eq(AlarmRecord::getState,AlarmRecordStateEnum.UNHANDLED));

        Map<AlarmLevelEnum, Long> count = alarmRecords.stream().collect(Collectors.groupingBy(AlarmRecord::getLevel,Collectors.counting()));

        if(count.get(AlarmLevelEnum.CRITICAL)!=null) {
            res.setCriticalNum(Math.toIntExact(count.get(AlarmLevelEnum.CRITICAL)));
        }

        if(count.get(AlarmLevelEnum.IMPORTANT)!=null) {
            res.setImportantNum(Math.toIntExact(count.get(AlarmLevelEnum.IMPORTANT)));
        }

        if(count.get(AlarmLevelEnum.SECONDARY)!=null) {
            res.setSecondaryNum(Math.toIntExact(count.get(AlarmLevelEnum.SECONDARY)));
        }

        if(count.get(AlarmLevelEnum.NOTIFY)!=null) {
            res.setNotifyNum(Math.toIntExact(count.get(AlarmLevelEnum.NOTIFY)));
        }

        return res;
    }

    /**
     * 根据云仓id列表获取告警数
     */
    @Override
    public Map<Integer, AlarmCountVO> getAlarmCount(List<Integer> yuncangIds) {
        Map<Integer, AlarmCountVO> result = new HashMap<>();
        if(CollUtil.isEmpty(yuncangIds)){
            return result;
        }

        // 获取所有为处理告警
        List<AlarmRecord> alarmRecords = alarmRecordDAO.list(Wrappers.<AlarmRecord>lambdaQuery().select(AlarmRecord::getYuncangId,
                AlarmRecord::getLevel).in(AlarmRecord::getYuncangId, yuncangIds)
                .eq(AlarmRecord::getState, AlarmRecordStateEnum.UNHANDLED));

        if(CollUtil.isEmpty(alarmRecords)){
            return result;
        }

        // 遍历
        for(AlarmRecord alarmRecord:alarmRecords){
            AlarmCountVO alarmCountVO = result.get(alarmRecord.getYuncangId());
            if(alarmCountVO == null){
                alarmCountVO = new AlarmCountVO();
            }
            switch (alarmRecord.getLevel()){
                case NOTIFY:
                    alarmCountVO.setNotifyNum(alarmCountVO.getNotifyNum()+1);
                    break;
                case CRITICAL:
                    alarmCountVO.setCriticalNum(alarmCountVO.getCriticalNum()+1);
                    break;
                case IMPORTANT:
                    alarmCountVO.setImportantNum(alarmCountVO.getImportantNum()+1);
                    break;
                case SECONDARY:
                    alarmCountVO.setSecondaryNum(alarmCountVO.getSecondaryNum()+1);
            }
            result.put(alarmRecord.getYuncangId(), alarmCountVO);
        }
        return result;
    }

    /**
     * 分页查询 获取告警记录列表
     */
    @Override
    public IPage<AlarmRecordVO> getAlarmRecordByPage(Long pageNum, Long pageSize, Boolean all, List<AdvanceQueryConditionDTO> conditions) {
        // 获取用户关联云仓
        List<Integer> yuncangIds = yuncangUserService.getYuncangIds(AuthContextUtil.getLoginName());

        if(CollUtil.isEmpty(yuncangIds)){
            return new Page<>(pageNum,pageSize,0);
        }

        LambdaQueryWrapper<AlarmRecord> wrapper = WrappersUtil.lambdaQuery(conditions, AlarmRecord.class);
        // 云仓过滤
        wrapper.in(AlarmRecord::getYuncangId,yuncangIds);
        Page<AlarmRecord> pageAlarm = MybatisPlusPageUtil.getPage(pageNum, pageSize, all);

        IPage<AlarmRecord> page = alarmRecordDAO.page(pageAlarm, wrapper);
        // 构建返回实体
        IPage<AlarmRecordVO>  result = new Page<>(page.getCurrent(),page.getSize(),page.getTotal());

        if(CollUtil.isEmpty(page.getRecords())){
            return result;
        }

        List<AlarmRecord> alarmRecords = page.getRecords();

        Map<Integer,Yuncang> yuncangs = yuncangDAO.listByIds(yuncangIds).stream().collect(Collectors.toMap(Yuncang::getId, Function.identity()));

        List<AlarmRecordVO> alarmRecordVOs = new ArrayList<>();
        for (AlarmRecord alarmRecord:alarmRecords){
            AlarmRecordVO alarmRecordVO = new AlarmRecordVO();
            BeanUtil.copyProperties(alarmRecord,alarmRecordVO);
            // 获取云仓
            Yuncang yuncang = yuncangs.get(alarmRecord.getYuncangId());
            if(yuncang != null) {
                alarmRecordVO.setYuncangName(yuncang.getYuncangName());
                alarmRecordVO.setCollectorSn(yuncang.getCollectorSn());
            }
            alarmRecordVOs.add(alarmRecordVO);
        }

        result.setRecords(alarmRecordVOs);
        return result;
    }

    /**
     * 发送告警邮件
     */
    private void sendMail(AlarmSetting alarmSetting, String yuncangName) {
        // 获取告警设置中的联系人
        List<Operator> operators = operatorDAO.list(Wrappers.<Operator>lambdaQuery().eq(Operator::getId, alarmSetting.getOperatorId()));
        if(CollUtil.isEmpty(operators)){
            return;
        }

        HashMap<String, Object> map = new HashMap<>();
        // 获取告警记录
        AlarmRecord record = alarmRecordDAO.getOne(Wrappers.<AlarmRecord>lambdaQuery().eq(AlarmRecord::getYuncangId, alarmSetting.getYuncangId())
                .eq(AlarmRecord::getAlarmId, alarmSetting.getId())
        .eq(AlarmRecord::getState,AlarmRecordStateEnum.UNHANDLED));
        if(record == null){
            return;
        }

        map.put("alarmRecordId", record.getId());
        map.put("time", DateUtil.date());
        for (Operator operator:operators ) {
            if(StrUtil.isNotBlank(operator.getEmail())) {
                MailUtil.send(mailAccount, operator.getEmail(), "智慧云能仓预警信息", MyMailUtil.generateMailBody(DateUtil.now(), yuncangName,
                        generateUrl(map)), true);
            }
        }
    }


    /**
     * 生成邮件url  点击确认告警信息
    */
    private String generateUrl(HashMap<String, Object> map) {
        String code = AESUtil.encrypt(JSON.toJSONString(map));
        return website+ ALARM_URL_TAIL +code;
    }

    /**
     * 通过邮件处理告警记录  根据告警记录id处理告警记录  将告警记录标志为已处理
     * @param id: 告警记录id
     */
    private String alarmRecordProcess(Integer id) {
        AlarmRecord record = alarmRecordDAO.getById(id);

        if (record == null){
            return "未找到该预警记录";
        }
        record.setState(AlarmRecordStateEnum.HANDLED);
        alarmRecordDAO.updateById(record);

        // 推送告警记录
        pushAlarmCount(record.getYuncangId());

        return "预警处理成功。";
    }

}
